package com.netx.generics.util;
import com.netx.generics.basic.Checker;
import com.netx.generics.basic.Logger;
import com.netx.generics.basic.ErrorList;
import com.netx.generics.basic.IntegrityException;
import com.netx.generics.basic.Logger.LEVEL;
import com.netx.generics.time.Date;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormatSymbols;
import java.util.List;
import java.util.Collection;
import java.util.Comparator;


public class Tools {

	public static String toHTML(String source) {
		Checker.checkNull(source, "source");
		return HtmlFormatter.format(source);
	}

	public static String toSafeString(String source) {
		Checker.checkNull(source, "source");
		return AsciiFormatter.format(source);
	}

	public static Object createObject(Class<?> c, Class<?>[] params, Object[] args) {
		Checker.checkNull(c, "c");
		Checker.checkNullElements(params, "params");
		Checker.checkNull(args, "args");
		try {
			// Create "regular" (no constructor) enum:
			if(c.isEnum() && params.length==1 && params[0]==String.class && args.length==1 && args[0].getClass()==String.class) {
				@SuppressWarnings({ "unchecked", "rawtypes" })
				Object result = Enum.valueOf((Class<Enum>)c, (String)args[0]); 
				return result;
			}
			else {
				try {
					Constructor<?> cons = c.getConstructor(params);
					return cons.newInstance(args);
				}
				catch(NoSuchMethodException nsme) {
					// Did not find a direct match, let's look for a sub-class match:
					Constructor<?>[] constructors = c.getConstructors();
					for(Constructor<?> cons : constructors) {
						Class<?>[] consParams = cons.getParameterTypes();
						if(consParams.length == params.length) {
							boolean matchFound = true;
							for(int i=0; i<params.length; i++) {
								if(!consParams[i].isAssignableFrom(params[i])) {
									matchFound = false;
									break;
								}
							}
							if(matchFound) {
								return cons.newInstance(args);
							}
						}
					}
					// If it got here, no appropriate constructor has been found:
					throw nsme;
				}
			}
		}
		catch(NoSuchMethodException nsme) {
			throw new ConstructionException("requested constructor not found", nsme);
		}
		catch(IllegalAccessException iae) {
			throw new ConstructionException("requested constructor is not public", iae);
		}
		catch(InstantiationException ie) {
			throw new ConstructionException("class is an "+(c.isInterface()?"interface":"abstract class")+" and cannot be instantiated", ie);
		}
		catch(InvocationTargetException ite) {
			throw new ConstructionException("error while instantiating object: "+ite.getCause().toString(), ite.getCause());
		}
	}

	public static Object createObject(Class<?> c, Object[] args) {
		Checker.checkNull(c, "c");
		Checker.checkNullElements(args, "args");
		Class<?>[] params = new Class[args.length];
		for(int i=0; i<params.length; i++) {
			params[i] = args[i].getClass();
		}
		return createObject(c, params, args);
	}

	public static Object createObject(Class<?> c) {
		return createObject(c, new Class[0], new Object[0]);
	}

	public static enum ORDER {
		LEFT,
		RIGHT
	}

	private static ORDER _monthOrder = ORDER.RIGHT;
	private static ORDER _yearOrder = ORDER.RIGHT;
	
	public static Date parseDate(String date) {
		Checker.checkEmpty(date, "date");
		// prepare date value:
		Date dateValue = new Date();
		dateValue.setMonth(1);
		dateValue.setDay(1);
		// prepare input String:
		date = date.trim();
		date = date.replaceAll(",", "");
		// split date:
		char separator = '\0';
		if(date.contains(" ")) {
			separator = ' ';
		}
		else if(date.contains("-")) {
			separator = '-';
		}
		else if(date.contains("/")) {
			separator = '/';
		}
		else {
			// noop;
		}
		// check for illegal characters:
		String tmpDate = date.replaceAll(separator+"", "");
		for(int i=0; i<tmpDate.length(); i++) {
			if(!Character.isLetterOrDigit(tmpDate.charAt(i))) {
				return null;
			}
		}
		// start decoding:
		String[] parts = date.split("["+separator+"]");
		if(parts.length > 3) {
			return null;
		}
		else if(parts.length == 1) {
			// assume month:
			int month = _getMonth(parts[0]);
			if(month == -1) {
				return null;
			}
			else {
				dateValue.setMonth(month + 1);
				return dateValue;
			}
		}
		else {
			// try to find yyyy format year:
			int yearPosition = -1;
			for(int i=0; i<parts.length; i++) {
				if(parts[i].length()==4 && Strings.isInteger(parts[i])) {
					yearPosition = i;
					break;
				}
			}
			if(yearPosition != -1) {
				int textMonthPosition = _getTextMonthPosition(parts);
				// text month found:
				if(textMonthPosition != -1) {
					List<String> partList = Arrays.toList(parts);
					dateValue.setYear(new Integer(partList.remove(yearPosition)));
					int month = _getMonth(partList.remove(textMonthPosition));
					if(month == -1) {
						return null;
					}
					dateValue.setMonth(month + 1);
					if(partList.isEmpty()) {
						return dateValue;
					}
					else {
						try {
							dateValue.setDay(new Integer(partList.get(0)));
							return dateValue;
						}
						catch(NumberFormatException nfe) {
							return null;
						}
					}
				}
				// no text month, 2 fields:
				else if(parts.length == 2) {
					try {
						List<String> partList = Arrays.toList(parts);
						dateValue.setYear(new Integer(partList.remove(yearPosition)));
						dateValue.setMonth(new Integer(partList.get(0)));
						return dateValue;
					}
					catch(NumberFormatException nfe) {
						return null;
					}
				}
				// no text month, 3 fields:
				else {
					try {
						if(yearPosition == 0) {
							// yyyy-mm-dd format:
							dateValue.setYear(new Integer(parts[0]));
							dateValue.setMonth(new Integer(parts[1]));
							dateValue.setDay(new Integer(parts[2]));
							return dateValue;
						}
						else if(yearPosition == 2) {
							// dd-mm-yyyy format:
							dateValue.setDay(new Integer(parts[0]));
							dateValue.setMonth(new Integer(parts[1]));
							dateValue.setYear(new Integer(parts[2]));
							return dateValue;
						}
						else {
							return null;
						}
					}
					catch(IllegalArgumentException iae) {
						return null;
					}
				}
			}
			// no 4-digit year, 2 fields:
			else if(parts.length == 2) {
				// get month and day values:
				int textMonthPosition = _getTextMonthPosition(parts);
				int dayPos = -1;
				int month = -1;
				if(textMonthPosition == 0) {
					month = _getMonth(parts[0])+1;
					dayPos = 1;
				}
				else if(textMonthPosition == 1) {
					dayPos = 0;
					month = _getMonth(parts[1])+1;
				}
				else {
					// no text month found:
					try {
						if(_monthOrder == ORDER.LEFT) {
							month = new Integer(parts[0]);
							dayPos = 1;
						}
						else {
							dayPos = 0;
							month = new Integer(parts[1]);
						}
					}
					catch(NumberFormatException nfe) {
						return null;
					}
				}
				try {
					dateValue.setMonth(month);
					dateValue.setDay(new Integer(parts[dayPos]));
					return dateValue;
				}
				catch(IllegalArgumentException iae) {
					return null;
				}
			}
			// no 4-difit year, 3 fields:
			else if(parts.length == 3) {
				int yearPos = -1;
				int textMonthPosition = _getTextMonthPosition(parts);
				int dayPos = -1;
				int month = -1;
				int yearInc = 0;
				if(textMonthPosition != -1) {
					// text month:
					if(textMonthPosition == 0) {
						// english date format - mmm dd, yy:
						dayPos = 1;
						yearPos = 2;
					}
					else {
						// assume year from yearOrder:
						if(_yearOrder == ORDER.LEFT) {
							yearPos = 0;
							dayPos = textMonthPosition==1 ? 2 : 1;
						}
						else {
							yearPos = 2;
							dayPos = textMonthPosition==1 ? 0 : 1;
						}
					}
					month = _getMonth(parts[textMonthPosition])+1;
				}
				else {
					// no text month:
					try {
						yearInc = 2000;
						// assume year and month from xxxOrder:
						if(_yearOrder == ORDER.LEFT) {
							yearPos = 0;
							if(_monthOrder == ORDER.LEFT) {
								month = new Integer(parts[1]);
								dayPos = 2;
							}
							else {
								dayPos = 1;
								month = new Integer(parts[2]);
							}
						}
						else {
							yearPos = 2;
							if(_monthOrder == ORDER.LEFT) {
								month = new Integer(parts[0]);
								dayPos = 1;
							}
							else {
								dayPos = 0;
								month = new Integer(parts[1]);
							}
						}
					}
					catch(NumberFormatException nfe) {
						return null;
					}
				}
				dateValue.setDay(new Integer(parts[dayPos]));
				dateValue.setMonth(month);
				dateValue.setYear(new Integer(parts[yearPos]+yearInc));
				return dateValue;
			}
			else {
				throw new IntegrityException(parts.length);
			}
		}
	}
	
	private static int _getMonth(String monthName) {
		if(Character.isLetter(monthName.charAt(0))) {
			int month = -1;
			// set the correct case for month name:
			StringBuilder tmp = new StringBuilder(monthName.toLowerCase());
			tmp.setCharAt(0, Character.toUpperCase(monthName.charAt(0)));
			monthName = tmp.toString();
			if(monthName.length() == 3) {
				// short representation:
				month = Arrays.find(monthName, new DateFormatSymbols().getShortMonths());
			}
			else {
				// full representation:
				month = Arrays.find(monthName, new DateFormatSymbols().getMonths());
			}
			return month;
		}
		else {
			try {
				int i = new Integer(monthName).intValue();
				return (i>0 && i<=12) ? i : -1;
			}
			catch(NumberFormatException nfe) {
				return -1;
			}
		}
	}
	
	private static int _getTextMonthPosition(String[] array) {
		for(int i=0; i<array.length; i++) {
			if(Character.isLetter(array[i].charAt(0))) {
				return i;
			}
		}
		return -1;
	}
	
	public static void handleCriticalError(Throwable t) {
		System.err.print("[CRITICAL ERROR]: ");
		if(t != null) {
			t.printStackTrace(System.err);
		}
		else {
			System.err.println("(null exception)");
		}
	}
	
	public static boolean contains(Collection<Object> c, Object o, Comparator<Object> comparator) {
		Checker.checkNull(c, "c");
		Checker.checkNull(comparator, "comparator");
		for(Object element : c) {
			if(comparator.compare(element, o) == 0) {
				return true;
			}
		}
		return false;
	}
	
	public static void log(Logger logger, ErrorList el) {
		Checker.checkNull(logger, "logger");
		Checker.checkNull(el, "el");
		try {
			if(logger.getLevel() != LEVEL.NONE) {
				List<String> errors = el.getErrors();
				List<String> warnings = el.getWarnings();
				if(errors != null) {
					logger.error(el.getErrorsHeader(), null);
					for(String msg : errors) {
						logger.error(msg, null);
					}
				}
				if(logger.getLevel() != LEVEL.ERROR && warnings != null) {
					logger.warning(el.getWarningsHeader());
					for(String msg : warnings) {
						logger.warning(msg);
					}
				}
			}
		}
		catch(Throwable t) {
			handleCriticalError(t);
		}
	}
}